package com.xc.stock.domain.basic.service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ag.exception.EntityNotFoundException;
import com.ag.exception.SysTipsException;
import com.ag.utils.AgAssert;
import com.ag.utils.BigDecimals;
import com.ag.utils.CollectionUtils;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAUpdateClause;
import com.xc.stock.aop.annotation.DistributedLock;
import com.xc.stock.aop.distributedlock.DistributedLockTemplate;
import com.xc.stock.constant.AccountLogType;
import com.xc.stock.constant.AccountType;
import com.xc.stock.constant.Currency;
import com.xc.stock.constant.RoleEnum;
import com.xc.stock.domain.basic.entity.Account;
import com.xc.stock.domain.basic.entity.AccountAction;
import com.xc.stock.domain.basic.entity.TradeAcc;
import com.xc.stock.domain.basic.factory.AccountFactory;
import com.xc.stock.domain.basic.repository.AccountRepository;
import com.xc.stock.domain.basic.repository.CacheRepository;
import com.xc.stock.dto.model.AccountInfo;
import com.xc.stock.dto.model.AccountOperParam;
import com.xc.stock.dto.model.AccountSelector;
import com.xc.stock.dto.model.DestInfo;
import com.xc.stock.dto.model.IdentityAuthInfo;
import com.xc.stock.enhance.eventbus.EventBusListener;
import com.xc.stock.infrastructure.db.po.QAccountPO;
import com.xc.stock.utils.SpringUtils;
import com.xc.stock.utils.Utils;

import io.reactivex.functions.Function4;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * AccountService
 *
 * @author xlfd
 * @email xlfd@gmail.com
 * @created 2022/03/25
 */
@Slf4j
@Service
@EventBusListener
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AccountService {
	final AccountRepository repository;
	final AccountFactory factory;
	final AccountActionService accountActionService;
	final CacheRepository cache;
	final DistributedLockTemplate distributedLockTemplate;
	final static String ACCOUNT_LOCK_PREFIX = "ACCOUNT_LOCK_PREFIX_";

	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户即将收入")
	public AccountInfo willIncome(final String accountNo, final BigDecimal amount, final AccountOperParam accountOperParam) {
		return of().operDirectly(accountNo, AccountLogType.WILL_INCOME, amount, accountOperParam, (account, type, pre, after) -> {
			return accountActionService.actionLock(account, type, amount, pre, after, accountOperParam);
		});
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户即将收入_退款")
	public AccountInfo willIncomeRefund(final String accountNo, Integer actionId, final BigDecimal amount) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		
		AccountInfo aa = of().operDirectly(accountNo, AccountLogType.WILL_INCOME_REFUND, amount, accountAction.accountOperParam(), (account, type, pre, after) -> {
			return accountActionService.actionLockHandleRefund(account, type, amount, pre, after, actionId);
		});
		
		return aa;
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户即将收入_成功")
	public AccountInfo willIncomeSuccess(final String accountNo, Integer actionId) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		BigDecimal amount = accountAction.remaining();
		
		AccountInfo aa = of().operDirectly(accountNo, AccountLogType.WILL_INCOME_SUCCESS, amount, accountAction.accountOperParam(), (account, type, pre, after) -> {
			return accountActionService.actionLockHandleSuccessful(account, type, amount, pre, after, actionId);
		});
		
		return aa;
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "限制提现_通过")
	public void limitWithrawPass(final String accountNo, Integer actionId) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		BigDecimal amount = accountAction.remaining();
		if(BigDecimals.loeZero(amount)) return;
		Account account = getAccount(accountNo);
		if(account == null) {
			throw new EntityNotFoundException("找不到[" + accountNo + "]账户");
		}
		
		Utils.t().exec(status -> {
			boolean limitWithrawPass = accountActionService.repository().limitWithrawPass(accountAction);
			if(limitWithrawPass) {
				JPAUpdateClause updateClause = account.limitWithrawPass(amount);
				boolean flag = updateClause.execute() > 0;
				if(! flag) {
					log.warn("limitWithrawPass -> limitWithrawPass: 返回 false, accountNo: {}, updateClause: {}", accountNo, updateClause);
					throw new SysTipsException("系统错误: limitWithrawPass");
				}
			} else {
				log.warn("limitWithrawPass: 返回 false, actionId: {}", actionId);
			}
		});
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户冻结资金")
	public AccountInfo lock(final String accountNo, final BigDecimal amount, final AccountOperParam accountOperParam) {
		return of().operDirectly(accountNo, AccountLogType.LOCK, amount, accountOperParam, (account, type, pre, after) -> {
			return accountActionService.actionLock(account, type, amount.negate(), pre, after, accountOperParam);
		});
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户扣除冻结资金_部分")
	public AccountInfo lockSubtractPart(final String accountNo, Integer actionId, BigDecimal dudct) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		
		BigDecimal amount = dudct;
		BigDecimal remaining = dudct.negate();
		
		AccountInfo aa = of().operDirectly(accountNo, AccountLogType.LOCK_SUBTRACT, amount, accountAction.accountOperParam(), (account, type, pre, after) -> {
			return accountActionService.actionLockHandleSuccessful(account, type, remaining, pre, after, actionId);
		});
		
		return aa;
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户取消冻结资金_部分")
	public AccountInfo lockCancelPart(final String accountNo, Integer actionId, BigDecimal dudct, String failMsg) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		
		BigDecimal amount = dudct;
		BigDecimal remaining = dudct.negate();
		AccountInfo aa = of().operDirectly(accountNo, AccountLogType.LOCK_CANCEL, amount, accountAction.accountOperParam(), (account, type, pre, after) -> {
			return accountActionService.actionLockHandleFail(account, type, remaining, pre, after, actionId, failMsg);
		});
		
		return aa;
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户扣除冻结资金")
	public AccountInfo lockSubtract(final String accountNo, Integer actionId) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		
		BigDecimal remaining = accountAction.remaining();
		BigDecimal amount = remaining.abs();
		
		AccountInfo aa = of().operDirectly(accountNo, AccountLogType.LOCK_SUBTRACT, amount, accountAction.accountOperParam(), (account, type, pre, after) -> {
			return accountActionService.actionLockHandleSuccessful(account, type, remaining, pre, after, actionId);
		});
		
		return aa;
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户取消冻结资金")
	public AccountInfo lockCancel(final String accountNo, Integer actionId, String failMsg) {
		AccountAction accountAction = accountActionService.repository().find(actionId);
		AgAssert.notNull(accountAction, EntityNotFoundException.class, "找不到账户动作ID");
		
		BigDecimal remaining = accountAction.remaining();
		BigDecimal amount = remaining.abs();
		AccountInfo aa = of().operDirectly(accountNo, AccountLogType.LOCK_CANCEL, amount, accountAction.accountOperParam(), (account, type, pre, after) -> {
			return accountActionService.actionLockHandleFail(account, type, remaining, pre, after, actionId, failMsg);
		});
		
		return aa;
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户增加资金")
	public AccountInfo add(final String accountNo, final BigDecimal amount, final AccountOperParam accountOperParam) {
		return of().operDirectly(accountNo, AccountLogType.ADD, amount, accountOperParam, (account, type, pre, after) -> {
			return accountActionService.actionDirectly(account, type, amount, pre, after, accountOperParam);
		});
	}
	
	@DistributedLock(argNum = 1, lockNamePre = ACCOUNT_LOCK_PREFIX, desc = "账户扣除资金")
	public AccountInfo subtract(final String accountNo, final BigDecimal amount, final AccountOperParam accountOperParam) {
		return of().operDirectly(accountNo, AccountLogType.SUBTRACT, amount, accountOperParam, (account, type, pre, after) -> {
			//减 记得是负数
			return accountActionService.actionDirectly(account, type, amount.negate(), pre, after, accountOperParam);
		});
	}
	
	public AccountInfo addOrSubtract(String accountNo, BigDecimal amount, AccountOperParam accountOperParam) {
		if(BigDecimals.ltZero(amount)) {
			return of().subtract(accountNo, amount.negate(), accountOperParam);
		} else if(BigDecimals.gtZero(amount)) {
			return of().add(accountNo, amount, accountOperParam);
		} else {
			// 0 nothing todo
			log.info("accountNo-{}-addOrSubtract时操作金额为0, 不做处理.", accountNo);
			return null;
		}
	}
	
	public Account auth(Account account, IdentityAuthInfo info) {
		account.setDestName(info.getName());
		repository.save(account);
		return account;
	}
	
	/**
	 * 直接操作成功 
	 * @param accountLogType
	 * @param accountNo
	 * @param amount
	 * @param accountOperParam
	 * @return
	 */
	@Transactional
	public AccountInfo operDirectly(String accountNo, AccountLogType accountLogType, BigDecimal amount, AccountOperParam accountOperParam, Function4<Account, AccountLogType, JSONObject, JSONObject, AccountInfo> func) {
		if(BigDecimals.loeZero(amount)) return null;
		
		Account account = getAccount(accountNo);
		if(account == null) {
			throw new EntityNotFoundException("找不到[" + accountNo + "]账户");
		}
		
		account.checkOper(accountOperParam);
		JSONObject pre = account.amountThumb();
		JPAUpdateClause updateClause = account.oper(accountLogType, amount, accountOperParam);
		boolean flag = updateClause.execute() > 0;
		AccountInfo aae = null;
		if(! flag) {
			log.error("更新账户{}时更新失败.{}, 更新[{}]", accountNo, accountOperParam.getLogContent(), updateClause);
			throw new SysTipsException(String.format("更新[%s]账户失败", accountNo));
		} else {
			JSONObject after = account.amountThumb();
			try {
				aae = func.apply(account,accountLogType, pre, after);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		
		if(aae == null) {
			aae = new AccountInfo();
		}
		
		aae.setAccount(account);
		return aae;
	}

	public Account getAccount(String accountNo) {
		QAccountPO q = QAccountPO.accountPO;
		BooleanExpression expression = q.accountNo.eq(accountNo);
		Account entity = repository().findOne(expression).orElse(null);
		return entity;
	}
	
	public Account createAccount(AccountSelector selector, TradeAcc tradeAcc, BigDecimal initAmount) {
		Account entity = factory.entity(selector, tradeAcc, initAmount);
		repository().save(entity);
		
		return entity;
	}
	
	public Account getAccount(AccountSelector acc) {
//		String accountNo = factory.accountNo(acc);
		DestInfo dest = acc.getDest();
		RoleEnum role = dest.getRe();
		AccountType accountType = acc.getAccountType();
		
		QAccountPO q = QAccountPO.accountPO;
//		BooleanExpression expression = q.accountNo.eq(accountNo);
		BooleanExpression expression = q.destId.eq(dest.getId())
				.and(q.role.eq(role.code()))
				.and(q.accountType.eq(accountType.code()))
				.and(q.currency.eq(acc.getCurrency().code()));
		
		Account entity = repository().findOne(expression).orElse(null);
		return entity;
	}
	
//	@Subscribe
//	public void e(UserRemovedEvent e) {
//		List<Account> list = repository.findAll(QAccountPO.accountPO.destId.eq(String.valueOf(e.getUser().getId())));
//		for (Account account : list) {
//			account.setStatus(StatusEnum.DISABLE.v());
//		}
//		
//		repository.saveAll(list);
//	}
	public List<Account> findAccount(List<DestInfo> dest, AccountType type, Currency currency) {
		if(CollectionUtils.isEmpty(dest)) {
			return Lists.newArrayList();
		}
		
		QAccountPO q = QAccountPO.accountPO;
		
		boolean count = dest.stream().map(val -> val.getRe()).count() == 1;
		AgAssert.isTrue(count, SysTipsException.class, "只能一种角色");
		RoleEnum re = dest.get(0).getRe();
		List<String> ids = Lists.transform(dest, val -> val.getId());
		BooleanExpression where = q.destId.in(ids)
			.and(q.role.eq(re.code()))
			.and(q.accountType.eq(type.code()))
			.and(q.currency.eq(currency.code()));
		
		return repository.findAll(where);
	}
	
	public Map<String, Account> findStockCnyAccountMap(List<DestInfo> dest) {
		return findAccount(dest, AccountType.STOCK, Currency.CNY)
				.stream()
				.collect(Collectors.toMap(val -> val.getDestId(), val -> val, (o, v) -> o));
	}
    
	public AccountRepository repository() {
		return repository;
	}

	public static AccountService of() {
		return SpringUtils.getBean(AccountService.class);
	}

	public long doLimitWithrawPassJob() {
		List<AccountAction> actionList = AccountActionService.of().repository().findNeedLimitWithrawPass();
		for (AccountAction accountAction : actionList) {
			try {
				limitWithrawPass(accountAction.getAccountNo(), accountAction.getId());
			} catch(Exception e) {
				log.error("doLimitWithrawPassJob失败: {}-{}", accountAction.getAccountNo(), accountAction.getId(), e);
			}
		}
		return 0;
	}
}
